﻿using System;
using System.Globalization;
using System.Runtime.Serialization;

namespace Hvac
{
    public enum SatisfyOption
    {
        MustSatisfy, ShouldSatisfy, MaySatisfy, DoNotSatisfy
    }

    public class DemandChangedEventArgs : EventArgs
    {
        // difference between setpoint and temperature
        readonly double demand;
        readonly SatisfyOption satisfyStatus;
        public double Demand { get { return demand; } }
        public SatisfyOption SatisfyStatus { get { return satisfyStatus; } }

        public DemandChangedEventArgs(double newDemand, SatisfyOption satisfyStatus)
        {
            this.demand = newDemand;
            this.satisfyStatus = satisfyStatus;
        }
    }

    [DataContract]
    public class Zone
    {
        public Zone(string name, IAirVolumeController volumeController, ITemperatureSensor sensor, double setPointLow, double setPointHigh, SatisfyOption satisfySetting)
        {
            _name = name;
            _airVolumeController = volumeController;
            _sensor = sensor;
            _setPointLow = setPointLow;
            _setPointHigh = setPointHigh;
            _satisfySetting = satisfySetting;

            _sensor.TemperatureChanged += new EventHandler<TemperatureChangedEventArgs>(temperatureSensor_TemperatureChanged);
            _lastTemperatureSensorValue = _sensor.Temperature;
        }

        public event EventHandler<DemandChangedEventArgs> DemandChanged;

        double _temperatureError;
        public double TemperatureError
        {
            get
            {
                return _temperatureError;
            }
        }

        SatisfyOption _satisfySetting;
        [DataMember]
        public SatisfyOption SatisfySetting
        {
            get { return _satisfySetting; }
            set
            {
                if (value != _satisfySetting)
                {
                    _satisfySetting = value;
                    computeTemperatureError(true);
                }
            }
        }

        double _setPointHigh;
        [DataMember]
        public double SetPointHigh
        {
            get { return _setPointHigh; }
            set
            {
                if (value != _setPointHigh)
                {
                    _setPointHigh = value;
                    computeTemperatureError(false);
                }
            }
        }

        double _setPointLow;
        [DataMember]
        public double SetPointLow
        {
            get { return _setPointLow; }
            set
            {
                if (value != _setPointLow)
                {
                    _setPointLow = value;
                    computeTemperatureError(false);
                }
            }
        }

        readonly private ITemperatureSensor _sensor;
        double _lastTemperatureSensorValue;
        const double hysteresis = 0.999;
        [DataMember]
        public double Temperature { get { return _lastTemperatureSensorValue; } private set { ; } }

        readonly IAirVolumeController _airVolumeController;
        public IAirVolumeController AirVolumeController
        {
            get { return _airVolumeController; }
            private set { ; } 
        }

        private string _name;
        [DataMember]
        public string Name
        {
            get { return _name; }
            set { _name = value; }
        }

        public override string ToString()
        {
            return _name;
        }

        void temperatureSensor_TemperatureChanged(object sender, TemperatureChangedEventArgs e)
        {
            _lastTemperatureSensorValue = e.Temperature;
            computeTemperatureError(false);
        }

        void computeTemperatureError(bool satisfyStatusChanged)
        {
            double demand = 0;
            // satisfyPriority represents the priority of the current temperatureError (this is sometimes different from the zone's standard priority)
            // MustSatisfy zones are downgraded here to ShouldSatisfy. If the temperatureError is greater than hysteresis,
            // the priority is increased back to MustSatisfy. 
            SatisfyOption satisfyPriority = (SatisfyOption)Math.Max((int)SatisfyOption.ShouldSatisfy, (int)_satisfySetting);

            //System.Diagnostics.Debug.WriteLine(string.Format(CultureInfo.CurrentCulture, "N:{4}, T:{0}, H:{1}, L:{2}, P:{3}", lastTemperatureSensorValue, setPointHigh, setPointLow, satisfyPriority, Name ));
            ///
            if (satisfyPriority == SatisfyOption.DoNotSatisfy)
            {
                demand = 0;
            }
            else if (_lastTemperatureSensorValue > _setPointHigh - hysteresis)
            {
                // Calculate the cooling demand
                demand = _setPointHigh - _lastTemperatureSensorValue - hysteresis;

                // if we previously requested heating and have overshot the setPoint, then make demand zero so that we don't 
                // enter a terrible loop of constanstly changing the mode
                if (_temperatureError > 0)
                    demand = 0;

                // The only time this matters is if the zone is a MustSatisfy
                if (_lastTemperatureSensorValue > _setPointHigh + hysteresis)
                    satisfyPriority = this.SatisfySetting;

            }
            else if (_lastTemperatureSensorValue < _setPointLow + hysteresis)
            {
                // Calcuate the heating demand
                demand = (_setPointLow - _lastTemperatureSensorValue) + hysteresis;

                // if we previously requested cooling and have overshot the setPoint, then adjust here
                if (_temperatureError < 0)
                    demand = 0;

                // The only time this matters is if the zone is a MustSatisfy
                if (_lastTemperatureSensorValue < _setPointLow - hysteresis)
                    satisfyPriority = this.SatisfySetting;
            }

            if (demand == 0)
                satisfyPriority = SatisfyOption.DoNotSatisfy;

            //System.Diagnostics.Debug.WriteLine(string.Format(CultureInfo.CurrentCulture, " P:{0}, D:{1}", satisfyPriority, demand));

            if (demand != _temperatureError || satisfyStatusChanged)
            {
                _temperatureError = demand;
                OnDemandChanged(new DemandChangedEventArgs(demand, satisfyPriority));
            }
        }

        protected virtual void OnDemandChanged(DemandChangedEventArgs e)
        {
            if (DemandChanged != null)
                DemandChanged(this, e);
        }
    }
}
